package org.bronycorps.mlptss.domain.layer;

import java.util.ArrayList;
import java.util.List;

import org.bronycorps.mlptss.domain.actor.AbstractActor;
import org.bronycorps.mlptss.domain.actor.BonusActor;
import org.bronycorps.mlptss.domain.actor.BossActor;
import org.bronycorps.mlptss.domain.actor.BulletActor;
import org.bronycorps.mlptss.domain.actor.EnemyActor;
import org.bronycorps.mlptss.domain.actor.HighlightActor;
import org.bronycorps.mlptss.domain.actor.MissileActor;
import org.bronycorps.mlptss.domain.actor.PlayerActor;
import org.bronycorps.mlptss.domain.actor.SpecialBulletActor;
import org.bronycorps.mlptss.domain.controller.AbstractPlayerController;
import org.bronycorps.mlptss.domain.controller.BonusController;
import org.bronycorps.mlptss.domain.controller.BossController;
import org.bronycorps.mlptss.domain.controller.BulletController;
import org.bronycorps.mlptss.domain.controller.EnemyController;
import org.bronycorps.mlptss.domain.controller.GameController;
import org.bronycorps.mlptss.domain.controller.PlayerComputer;
import org.bronycorps.mlptss.domain.controller.PlayerHuman;
import org.bronycorps.mlptss.domain.controller.SpecialBulletController;
import org.bronycorps.mlptss.domain.model.Bonus;
import org.bronycorps.mlptss.domain.model.Bonus.BonusType;
import org.bronycorps.mlptss.domain.model.Boss;
import org.bronycorps.mlptss.domain.model.Bullet;
import org.bronycorps.mlptss.domain.model.BulletType;
import org.bronycorps.mlptss.domain.model.Enemy;
import org.bronycorps.mlptss.domain.model.Player;
import org.bronycorps.mlptss.domain.model.Player.State;
import org.bronycorps.mlptss.domain.model.SpecialBullet;
import org.bronycorps.mlptss.domain.model.SpecialBulletType;
import org.bronycorps.mlptss.domain.model.event.BombEvent;
import org.bronycorps.mlptss.domain.model.event.BombEventListener;
import org.bronycorps.mlptss.domain.model.event.BreakShieldEvent;
import org.bronycorps.mlptss.domain.model.event.BreakShieldEventListener;
import org.bronycorps.mlptss.domain.model.event.CreateMissileEvent;
import org.bronycorps.mlptss.domain.model.event.CreateMissileEventListener;
import org.bronycorps.mlptss.domain.model.event.MatchOverEvent;
import org.bronycorps.mlptss.domain.model.event.MatchOverEventListener;
import org.bronycorps.mlptss.domain.model.event.ShootEvent;
import org.bronycorps.mlptss.domain.model.event.ShootEventListener;
import org.bronycorps.mlptss.domain.model.event.SpecialAttackEvent;
import org.bronycorps.mlptss.domain.model.event.SpecialAttackEventListener;
import org.bronycorps.mlptss.domain.model.type.EnemyColor;
import org.bronycorps.mlptss.domain.model.type.GameState;
import org.bronycorps.mlptss.domain.tweenable.HighlightActorAccessor;
import org.bronycorps.mlptss.manager.BulletFactory;
import org.bronycorps.mlptss.manager.TrajectFactory;
import org.bronycorps.mlptss.manager.TrajectFactory.BulleTTrajectType;
import org.bronycorps.mlptss.manager.ai.PredictionManager;
import org.bronycorps.mlptss.manager.preferences.AssetsConfiguration.BossConfiguration;
import org.bronycorps.mlptss.manager.preferences.AssetsConfiguration.EnemyConfiguration;
import org.bronycorps.mlptss.manager.preferences.GameConfiguration;
import org.bronycorps.mlptss.manager.preferences.WavesConfiguration.WaveConfiguration;
import org.bronycorps.mlptss.manager.preferences.WavesConfiguration.WaveUnitConfiguration;

import aurelienribon.tweenengine.BaseTween;
import aurelienribon.tweenengine.Timeline;
import aurelienribon.tweenengine.Tween;
import aurelienribon.tweenengine.TweenCallback;
import aurelienribon.tweenengine.TweenManager;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Group;

public class Track extends Group implements ShootEventListener,
		SpecialAttackEventListener, MatchOverEventListener, BombEventListener,
		BreakShieldEventListener {
	private BonusActor bonusActor;
	private BossActor boss = null;
	private BossConfiguration bossConfig;
	private final List<BulletActor> bullets = new ArrayList<BulletActor>();
	private float comboCooldown = 0;
	private int comboMultiplier = 0;
	private List<CreateMissileEventListener> createMissileEventListeners = new ArrayList<CreateMissileEventListener>();
	private final List<EnemyActor> enemies = new ArrayList<EnemyActor>();
	private EnemyConfiguration enemyConfig;
	private GameController gameController;

	private HighlightActor highlightActor;

	private int idWave = 0;

	private boolean isBombExploding = false;
	private boolean isWaitingNextWave = false;

	private boolean isWaveOnHold = false;

	private PlayerActor player = null;

	private PredictionManager predictionManager = null;
	private final List<SpecialBulletActor> specialBullets = new ArrayList<SpecialBulletActor>();
	private TrajectFactory trajectFactory;
	private float waveCooldown = 0; // [s]

	public Track() {
		super();

		this.trajectFactory = TrajectFactory.getInstance();
		this.gameController = GameController.getInstance();
		this.enemyConfig = this.gameController.getEnemyConfiguration();

		this.gameController.addListener(this);

		this.highlightActor = new HighlightActor();
		addActor(this.highlightActor);
	}

	@Override
	public void act(float delta) {
		super.act(delta);

		if (!this.isWaveOnHold
				&& this.gameController.getState() == GameState.GAME
				&& this.isWaitingNextWave) {
			this.waveCooldown = -delta;
			if (this.waveCooldown <= 0) {
				this.isWaitingNextWave = false;
				createWave();
			}
		}

		if (this.comboCooldown > 0) {
			this.comboCooldown -= delta;
			if (this.comboCooldown <= 0) {
				this.comboCooldown = 0;
				this.comboMultiplier = 0;
			}
		}

		if (this.predictionManager != null) {
			this.predictionManager.update(delta);
		}
	}

	public void addComboMultiplier(int count) {
		this.comboMultiplier += count;
	}

	public boolean addListener(CreateMissileEventListener e) {
		return this.createMissileEventListeners.add(e);
	}

	public void clearAISnapshots() {
		if (this.player.getPlayer() instanceof PlayerComputer) {
			((PlayerComputer) this.player.getPlayer()).getAiManager()
					.clearSnapshots();
		}
	}

	private void createBonus(BonusType bonusType) {
		Bonus bonus = new Bonus(bonusType);
		BonusController controller = new BonusController(bonus);
		this.bonusActor = new BonusActor(controller);

		addActor(this.bonusActor);
	}

	public void createBoss(Player owner, Track sourceTrack) {
		if (this.boss == null) {
			Boss model = new Boss(sourceTrack.getBossConfig(), owner);
			this.boss = new BossActor(new BossController(model));

			addActor(this.boss);

			this.trajectFactory.generateBossTraject(this.boss);
			this.trajectFactory.tweenBoss(this.boss);

		}
	}

	public void createComputerPlayer(Player newPlayer) {
		if (this.player != null) {
			return;
		}
		PlayerComputer playerComputer = new PlayerComputer(newPlayer);
		createPlayer(playerComputer);

		this.predictionManager = new PredictionManager();
		playerComputer.setPredictionManager(this.predictionManager);
	}

	public void createEnemy(Vector2 origin, WaveUnitConfiguration config) {
		EnemyColor enemyColor = EnemyColor.getColorByLevel(config.getColor());

		Enemy enemy = new Enemy(this.enemyConfig, enemyColor, config.isShield());
		enemy.setPosition(origin);
		EnemyActor blocActor = new EnemyActor(new EnemyController(enemy));
		addActor(blocActor);
		this.enemies.add(blocActor);

		this.trajectFactory.tweenEnemy(blocActor, config.getTraject());

		if (this.predictionManager != null) {
			this.predictionManager.addEnemy(blocActor, config.getTraject());
		}
	}

	public void createHumanPlayer(Player newPlayer) {
		if (this.player != null) {
			return;
		}
		createPlayer(new PlayerHuman(newPlayer));
	}

	private void createPlayer(AbstractPlayerController newPlayer) {
		this.player = new PlayerActor(newPlayer);

		Player model = (Player) this.player.getModel();

		model.getPosition().set(
				GameConfiguration.getInstance().getPlayerOriginalPosition());

		addActor(this.player);

		model.addListener((ShootEventListener) this);
		model.addListener((BombEventListener) this);
		model.addListener((BreakShieldEventListener) this);
		model.addListener((SpecialAttackEventListener) this);

		this.bossConfig = GameConfiguration.getInstance().getCurrentMod()
				.getAssetsConfiguration()
				.getBossByName(model.getCharacter().getBossName());
	}

	private void createWave() {
		// first clear the stage
		removeBonus();

		// then create next wave
		WaveConfiguration wave = GameController.getInstance().getWaveFactory()
				.getWave(this.idWave);

		final Vector2 origin = new Vector2(getWidth() + 200, getHeight() / 2);

		for (WaveUnitConfiguration unit : wave.getUnits()) {
			createEnemy(origin, unit);
		}

		if (wave.isShieldBonus()) {
			createBonus(BonusType.EXPLODE);
		} else if (shouldCreateBonus()) {
			createBonus(BonusType.BOMB);
		}
	}

	private void explodeAllEnemies() {
		// explode all current enemies
		for (EnemyActor enemy : this.enemies) {
			enemy.getEnemyController().getModel().terminate(false);
		}
	}

	public void fireCreateMissileEvent(CreateMissileEvent event) {
		if (!this.isBombExploding || event.getSource() instanceof MissileActor) {
			for (CreateMissileEventListener listener : this.createMissileEventListeners) {
				listener.onCreateMissile(event);
			}
		}
	}

	public BonusActor getBonusActor() {
		return this.bonusActor;
	}

	public BossConfiguration getBossConfig() {
		return this.bossConfig;
	}

	public List<BulletActor> getBullets() {
		return this.bullets;
	}

	public float getComboCooldown() {
		return this.comboCooldown;
	}

	public int getComboMultiplier() {
		return this.comboMultiplier;
	}

	public List<EnemyActor> getEnemies() {
		return this.enemies;
	}

	public PlayerActor getPlayer() {
		return this.player;
	}

	public PredictionManager getPredictionManager() {
		return this.predictionManager;
	}

	public List<SpecialBulletActor> getSpecialBullets() {
		return this.specialBullets;
	}

	@Override
	public void onBreakShield(BreakShieldEvent event) {
		for (EnemyActor enemy : this.enemies) {
			((Enemy) enemy.getModel()).breakShield();
		}
	}

	@Override
	public void onExplode(BombEvent event) {

		this.isBombExploding = true;

		final TweenManager tweenManager = this.trajectFactory.getTweenManager();

		final Player playerModel = (Player) this.player.getModel();

		// plan invincibility and enemy waves
		this.isWaveOnHold = true;
		playerModel.setState(State.INVINCIBLE);
		Tween.call(new TweenCallback() {
			@Override
			public void onEvent(int type, BaseTween<?> source) {
				playerModel.setState(State.NORMAL);
				// resume waves
				Track.this.isWaveOnHold = false;
				Track.this.isBombExploding = false;
			}
		}).delay(5f).start(tweenManager);

		// plan explode all enemies
		Tween.call(new TweenCallback() {
			@Override
			public void onEvent(int type, BaseTween<?> source) {
				explodeAllEnemies();
			}
		}).delay(0.5f).start(tweenManager);

		// colorize
		// setActorsTint(new Color(0, 0, 0, 1));
		Timeline.createSequence()
				.beginSequence()
				.push(Tween.to(this.highlightActor,
						HighlightActorAccessor.ALPHA, 0.5f).target(1))
				.pushPause(2)
				.push(Tween.to(this.highlightActor,
						HighlightActorAccessor.ALPHA, 2f).target(0)).end()
				.start(tweenManager);
	}

	@Override
	public void onMatchOver(MatchOverEvent event) {
		explodeAllEnemies();
		removeBonus();

	}

	@Override
	public void onShoot(ShootEvent event) {
		Player player = (Player) event.getSource();
		BulletType bulletType = BulletFactory.getBulletType(player
				.getCharacter().getBulletTypeName());
		BulletActor bullet = new BulletActor(new BulletController(new Bullet(
				bulletType, player)));

		bullet.setInitialPosition(new Vector2(this.player.getModelPosition())
				.add(20, 0));

		addActor(bullet);

		this.bullets.add(bullet);

		this.trajectFactory.tweenBullet(bullet, BulleTTrajectType.SIMPLE);

	}

	@Override
	public void onShoot(SpecialAttackEvent event) {
		Player player = (Player) event.getSource();

		SpecialBulletType bulletType = BulletFactory
				.getSpecialBulletType(player.getCharacter()
						.getSpecialBulletName());

		SpecialBulletActor specialBullet = new SpecialBulletActor(
				new SpecialBulletController(new SpecialBullet(player,
						bulletType)));

		specialBullet.setInitialPosition(new Vector2(this.player
				.getModelPosition()));

		addActor(specialBullet);

		this.specialBullets.add(specialBullet);

	}

	public void removeBonus() {
		if (this.bonusActor != null) {
			removeActor(this.bonusActor);
			this.bonusActor.clearActions();

			this.bonusActor = null;
		}
	}

	public void removeBoss(BossActor bossActor) {
		if (this.boss != null && this.boss == bossActor) {
			removeActor(this.boss);
			this.boss = null;
		}
	}

	public void removeBullet(BulletActor bulletActor) {
		this.bullets.remove(bulletActor);
		removeActor(bulletActor);
		bulletActor.clearActions();
		TrajectFactory.getInstance().clearTweens(bulletActor);
	}

	public void removeBullet(SpecialBulletActor specialBulletActor) {
		this.specialBullets.remove(specialBulletActor);

		removeActor(specialBulletActor);
		specialBulletActor.clearActions();
		TrajectFactory.getInstance().clearTweens(specialBulletActor);
	}

	public void removeEnemy(EnemyActor enemyActor) {
		this.enemies.remove(enemyActor);
		removeActor(enemyActor);
		enemyActor.clearActions();
		TrajectFactory.getInstance().clearTweens(enemyActor);

		if (this.enemies.size() == 0) {
			// end of the wave. Time to start next wave
			startNextWave();
		}

		if (this.predictionManager != null) {
			this.predictionManager.removeEnemy(enemyActor);
			clearAISnapshots();
		}
	}

	public boolean removeListener(Object o) {
		return this.createMissileEventListeners.remove(o);
	}

	private void setActorsTint(Color tint) {
		float playerAlpha = this.player.getColor().a;
		this.player.getColor().set(tint);
		this.player.getColor().a = playerAlpha;

		for (AbstractActor actor : this.enemies) {
			actor.setColor(tint);
		}
	}

	public void setComboCooldown(float comboCooldown) {
		this.comboCooldown = comboCooldown;
	}

	private boolean shouldCreateBonus() {
		return Math.floor(Math.random() * 20) == 0;
	}

	public void StartGame(int initialIdWave) {
		this.idWave = initialIdWave;
		createWave();
	}

	private void startNextWave() {
		if (this.gameController.isMatchOver()) {
			return;
		}
		this.idWave = GameController.getInstance().getWaveFactory()
				.getNextWaveId(this.idWave);

		this.waveCooldown = GameConfiguration.getInstance().getWaveCooldown();
		this.isWaitingNextWave = true;
	}
}
